package com.pactera.asmp.server.shiro;

import java.util.List;
import java.util.Set;

import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.SimplePrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.subject.support.DefaultSubjectContext;
import org.crazycake.shiro.exception.SerializationException;
import org.crazycake.shiro.serializer.ObjectSerializer;
import org.crazycake.shiro.serializer.RedisSerializer;
import org.crazycake.shiro.serializer.StringSerializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;

import com.pactera.asmp.server.common.constants.Constants;
import com.pactera.asmp.server.common.domain.Audience;
import com.pactera.asmp.server.common.utils.JwtTokenUtil;
import com.pactera.asmp.server.dao.UserMapper;
import com.pactera.asmp.server.pojo.Permission;
import com.pactera.asmp.server.pojo.Role;
import com.pactera.asmp.server.pojo.User;
import com.pactera.asmp.server.service.AuthService;
import com.pactera.asmp.server.service.UserServiceImpl;

import io.jsonwebtoken.Claims;

//@Service
public class ShiroRealm extends AuthorizingRealm {

	private static final Logger logger = LoggerFactory
			.getLogger(UserServiceImpl.class);
	@Autowired
	private UserMapper userMapper;
	@Autowired
	private AuthService authService;
    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private Audience audience;

    public static final String KEY_SHIRO_PREFIX = "shiro:cache:";

    public static final String KEY_SESSION_PREFIX = "shiro:session:";

    public static final String KEY_AUTHOR_PREFIX = ".authorizationCache";

    public static final String KEY_ACTIVESESSION_PREFIX = "shiro:cache:shiro-activeSessionCache:";
	/**
	 * 授予角色和权限
	 * @param principalCollection
	 * @return
	 */
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(
			PrincipalCollection principalCollection) {
		//授权
		logger.debug("授予角色和权限");
		// 添加权限 和 角色信息
		SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
		// 获取当前登陆用户
		Subject subject = SecurityUtils.getSubject();
		User user = (User) subject.getPrincipal();
		// 硬编码
//		if (user.getUsername().equals("lzqzzu")) {
//			// 超级管理员，添加所有角色、添加所有权限
//			authorizationInfo.addRole("*");
//			authorizationInfo.addStringPermission("*");
//		} else {
			// 普通用户，查询用户的角色，根据角色查询权限
			Integer userId = user.getId();
			List<Role> roles = this.authService.getRoleByUser(userId);
			if (null != roles && roles.size() > 0) {
				for (Role role : roles) {
					authorizationInfo.addRole(role.getCode());
					// 角色对应的权限数据
					List<Permission> perms = this.authService.findPermsByRoleId(role
							.getId());
					if (null != perms && perms.size() > 0) {
						// 授权角色下所有权限
						for (Permission perm : perms) {
							authorizationInfo.addStringPermission(perm
									.getCode());
						}
					}
				}
			}
//		}
		return authorizationInfo;
	}

	/**
	 * 登录认证
	 * @param authenticationToken
	 * @return
	 * @throws AuthenticationException
	 */
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(
			AuthenticationToken authenticationToken)
			throws AuthenticationException {
		//TODO
		//UsernamePasswordToken用于存放提交的登录信息
		UsernamePasswordToken token = (UsernamePasswordToken)authenticationToken;
		logger.info("用户登录认证：验证当前Subject时获取到token为：" + ReflectionToStringBuilder.toString(token, ToStringStyle.MULTI_LINE_STYLE));
		String userName = token.getUsername();
		// 调用数据层
		User user = userMapper.findUserByName(userName);
		logger.debug("用户登录认证！用户信息user：" + user);
		if (user == null) {
			// 用户不存在
			return null;
		} else {
			// 密码存在
			// 第一个参数 ，登陆后，需要在session保存数据
			// 第二个参数，查询到密码(加密规则要和自定义的HashedCredentialsMatcher中的HashAlgorithmName散列算法一致)
			// 第三个参数 ，realm名字
			return new SimpleAuthenticationInfo(user, DigestUtils.md5Hex(user.getPassword()),
					getName());
		}
	}

	/**
	 * 清除所有缓存【实测无效】
	 */
	public void clearCachedAuth(){
		this.clearCachedAuthorizationInfo(SecurityUtils.getSubject().getPrincipals());
	}

    /**
     * 通过userId清除指定登陆者的缓存
     * @param uid
     * @param userName
     * @param author
     * @param out
     * @throws Exception
     */
    public void clearAuthByUserId(Integer uid, String userName, Boolean author, Boolean out) throws Exception{
        //获取所有session
        Set<String> keys = redisTemplate.keys(KEY_SESSION_PREFIX.concat("*"));
        if (keys != null && keys.size() > 0) {
            for (String key:keys) {
                try {
                    RedisSerializer valueSerializer = new ObjectSerializer();
                    byte[] sessionBytes= redisTemplate.execute(new RedisCallback<byte[]> () {
                        @Override
                        public byte[] doInRedis(RedisConnection connection) throws DataAccessException {
                            try {
                                StringSerializer stringSerializer = new StringSerializer();
                                return connection.get(stringSerializer.serialize(key));
                            } catch (SerializationException e) {
                                logger.error("clearAuthByUserId error:{}", e.getMessage());
                                return null;
                            }
                        }
                        
                    });
                    if(null == sessionBytes || sessionBytes.length == 0) {
                        return ;
                    }
                    Session session = (Session)valueSerializer.deserialize(sessionBytes) ;
                    Object obj = session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
                    if(obj instanceof SimplePrincipalCollection){
                        //强转
                        SimplePrincipalCollection spc = (SimplePrincipalCollection)obj;
                        User user = new User();
                        BeanUtils.copyProperties(spc.getPrimaryPrincipal(),user);
                        //判断用户，匹配用户ID。
                        if(uid.intValue() == user.getId().intValue()){
                            if(author)
                                this.clearCachedAuthorizationInfo(spc);
                            if(out){
                                redisTemplate.delete(KEY_SESSION_PREFIX + session.getId());
                            }
                            break;
                        }
                    }
                } catch (Exception e) {
                    logger.error("clearAuthByUserId error:{}", e.getMessage());
                    throw new Exception(e.getMessage());
                }
            }
        }
        if(author) {
            String authKey = StringUtils.join(KEY_SHIRO_PREFIX, this.getClass().getName(), KEY_AUTHOR_PREFIX, ":", uid);
            if(redisTemplate.hasKey(authKey)) {
                redisTemplate.delete(authKey);
            }
            String authToSessionKey = StringUtils.join(KEY_ACTIVESESSION_PREFIX, userName);
            if(redisTemplate.hasKey(authToSessionKey)) {
                redisTemplate.delete(authToSessionKey);
            }
        }
        
    }

    /**
     * 清除登录者的token
     * @param token
     */
    public void clearToken(String token){
        String atoken =token.substring(7);
        Claims claims = JwtTokenUtil.parseJWT(atoken,audience.base64Secret); // 验证token (过期或无效)
        if (null == claims || null == claims.get("uuid") || null == claims.get("channelId")) {
            return;
        }
        int uuid = Integer.parseInt(claims.get("uuid", String.class));
        int channelId = claims.get("channelId", Integer.class);
        if (!StringUtils.equals(redisTemplate.opsForValue().get(Constants.REDIS_TOKEN_KEY + channelId + ":" + uuid), token)) {
            return;
        }
        redisTemplate.delete(Constants.REDIS_TOKEN_KEY + channelId + ":" + uuid);
    }
}
